#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# Copyright 2022 Huaqin Technology. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#    * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#    * Neither the name of Huaqin Technology nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import subprocess
import argparse
import os
import time
import datetime
import threading
import re
import sys
import csv
import json
import pprint
import signal
import xml.etree.ElementTree as XMLET

_WORKING_ROOT_PATH = os.getcwd() + '/' + 'working/'

_PROJECT_CONFIG_INFO = {
  'TS6569A' : {
    'platform_type' : 'bes',
    'code_server' : 'gerritslavesh.huaqin.com:29418',
    'cherrypick_server' : 'gerrit.huaqin.com:29418',
    'manifest' : 'BD1SW/tws_master_202208301600.xml',
    'earphone_build_cmd' : './build.sh ts6569a',
    'earphone_build_dir' : 'earphone/',
    'earphone_build_test_file' : 'earphone/build.sh',
    'earphone_build_output_path' : 'earphone/out/',
    'earphone_build_output_file_path' : 'earphone/out/ts6569a/user/',
    'earphone_build_output_file_list' : ['_best1000.lds', 'audio_res.bin', 'ts6569a.bin', 'ts6569a.elf', 'ts6569a.lst', 'ts6569a.map', 'ts6569a.str'],
    'img_file_dict' : {'earbud' : {'map' : 'ts6569a.map', 'bin' : 'ts6569a.bin'}},
    'twslint_project' : {'HQ_BD1SW_PA1952/earphone'},
    'res_check_project' : {'HQ_BD1SW_PA1952/earphone'},
    'xml' : 'BD1SW/tws_master_202208301600.xml'
  },
}

# account_id, code_server, manifest
_CODE_PULL_CMD_TEMPLATE = 'repo init --no-repo-verify -u ssh://%s@%s/manifest -m %s  --depth=1 && repo sync -j8 -c --no-tags && repo start dev --all'

# account_id, cherrypick_server, _gerrit_project, _gerrit_ref_info
# _CHERRYPICK_CMD_TEMPLATE = 'git fetch ssh://101002563@gerrit.huaqin.com:29418/HQ_BD1SW_PA1952/earphone refs/changes/78/256878/1 && git cherry-pick FETCH_HEAD'
_CHERRYPICK_CMD_TEMPLATE = 'git fetch ssh://%s@%s/%s %s && git cherry-pick FETCH_HEAD'

# port, account_id, codeserver, comment_message, num, patchset
# ssh -p 29418 101002563@gerrit.huaqin.com gerrit review -m \"Build Successful.\" 256901,2
_GERRIT_REVIEW_CMD_TEMPLATE = 'ssh -p %s %s@%s gerrit review -m %s %s,%s'

LEVEL_ERROR = '[Error]'
LEVEL_INFO = '[Info]'
LEVEL_WARN = '[Warning]'
def LOG(level, msg):
  def time_str():
    return time.strftime('%Y-%m-%d %H:%M:%S  ', time.localtime())
  log_msg = time_str() + level + ': ' + str(msg)
  print(log_msg)
  if level == LEVEL_ERROR:
    exit(1)


def TIMEOUT_SHELL_COMMAND(command:str, timeout=60, capture_output=False, encoding ='utf8', cwd=None):
  # cmd = command
  # start = datetime.datetime.now()
  # if mode == 'pipe':
    # process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding=encode_mode, cwd=working_path)

  #   while process.poll() is None:
  #     time.sleep(0.2)
  #     now = datetime.datetime.now()
  #     if (now - start).seconds > timeout:
  #       os.kill(process.pid, signal.SIGKILL)
  #       os.waitpid(-1, os.WNOHANG)
  #       LOG(LEVEL_INFO, 'Process CMD TIMEOUT: [{}] [{}] [{}] [{}]'.format(command, mode, timeout, working_path))
  #       return 2
  #   LOG(LEVEL_INFO, 'Process CMD SUCCESSFUL: [{}] [{}] [{}] [{}]'.format(command, mode, timeout, working_path))

  #   return process.stdout.readlines()
  # else:
  #   process = subprocess.run(cmd, shell=True)
  cmd = command
  LOG(LEVEL_INFO, 'Process CMD START: [cmd={}] [timeout={}] [capture_out={}] [encoding={}] [cwd={}]'.format(cmd, timeout, capture_output, encoding, cwd))

  process = subprocess.run(cmd, shell=True, timeout=timeout, capture_output=capture_output, encoding=encoding, cwd=cwd)
  LOG(LEVEL_INFO, 'Process CMD END: [returncode={}] [cmd={}] [timeout={}] [capture_out={}] [encoding={}] [cwd={}]'.format(process.returncode ,cmd, timeout, capture_output, encoding, cwd))
  if process.stdout:
    # LOG(LEVEL_INFO, 'CMD stdout = [{}]'.format(process.stdout))
    # if process.stderr:
    #   LOG(LEVEL_INFO, 'CMD stderr = [{}]'.format(process.stderr))
    if (not process.returncode):
      return [process.returncode, process.stdout.strip().split('\n')]
  else:
    return [process.returncode, []]


class _GuardInfo(object):
  def __init__(self) -> None:
    self.account_id = ''
    self.change_id = ''
    self.output_type = ''
    self.org_gerrit_data = []
    self._gerrit_project = []
    self._gerrit_project_count = 0
    self._gerrit_branch = ''
    self._gerrit_number = []
    self._gerrit_url = []
    self._gerrit_patchset_num = []
    self._gerrit_ref_info = []
    self._project_name = ''
    self._gerrit_file_list = []
    self._working_dir = ''
    self._code_dir = ''
    self._manifest_project_path_dict = []
    self._cherrypick_cmd_list = []
    self._image_path = {
      'earbud' : {'org' : 'earbud_img/org/', 'patch' : 'earbud_img/patch/'},
      'box' : {'org' : 'box_img/org/', 'patch' : 'box_img/patch'},
    }

  def _SetGerritProjectCount(self, count):
    self._gerrit_project_count = count
  
  def GetGerritProjectCount(self):
    return self._gerrit_project_count
  

  def _SetGerritProjectInfo(self, proj):
    """
    'project': 'HQ_BD1SW_PA1952/earphone'
    """
    self._gerrit_project.append(proj)
    self._SetGerritProjectCount(len(self._gerrit_project))
  
  def GetGerritProjectInfo(self, index):
    """
    'project': 'HQ_BD1SW_PA1952/earphone'
    """
    if index >= len(self._gerrit_project):
      LOG(LEVEL_ERROR, 'The count of the Gerrit Project is [{}], but you set index [{}]'.format(len(self._gerrit_project), index))
    return self._gerrit_project[index]


  def _SetGerritBranchInfo(self, info):
    """
    'branch': 'tws_master_202208301600'
    """
    if (self._gerrit_branch != '') and (self._gerrit_branch != info):
      LOG(LEVEL_ERROR, 'One ChangeID should not have Two branches: [{}] : [{}]'.format(self._gerrit_branch, info))
    self._gerrit_branch = info

  def GetGerritBranchInfo(self):
    """
    'branch': 'tws_master_202208301600'
    """
    return self._gerrit_branch


  def _SetGerritNumber(self, number:str):
    """
    'number': '256832'
    """
    if len(number) != 6:
      LOG(LEVEL_ERROR, 'Gerrit number should have 6 digits, but you set [{}]'.format(number))
    self._gerrit_number.append(number)

  def GetGerritNumber(self, index) -> str:
    """
    'number': '256832'
    """
    if index >= len(self._gerrit_number):
      LOG(LEVEL_ERROR, 'The count of the Gerrit Number is [{}], but you set index [{}]'.format(len(self._gerrit_number), index))
    return self._gerrit_number[index]


  def _SetGerritUrl(self, url:str):
    """
    'url': 'https://gerrit.huaqin.com:9443/256832'
    """
    self._gerrit_url.append(url)

  def GetGerritUrl(self, index) -> str:
    """
    'url': 'https://gerrit.huaqin.com:9443/256832'
    """
    if index >= len(self._gerrit_url):
      LOG(LEVEL_ERROR, 'The count of the Gerrit URL is [{}], but you set index [{}]'.format(len(self._gerrit_url), index))
    self._gerrit_url[index]
  

  def _SetGerritPatchsetNum(self, number:str):
    """
    'currentPatchSet': {'number': '3'
    """
    self._gerrit_patchset_num.append(number)
  
  def GetGerritPatchSetNum(self, index:int) -> str:
    """
    'currentPatchSet': {'number': '3'
    """
    if index >= len(self._gerrit_patchset_num):
      LOG(LEVEL_ERROR, 'The count of the Patchset Number is [{}], but you set index [{}]'.format(len(self._gerrit_patchset_num), index))
    return self._gerrit_patchset_num[index]
  

  def _SetGerritRefInfo(self, ref):
    """
    'currentPatchSet': {ref': 'refs/changes/32/256832/3'
    """
    self._gerrit_ref_info.append(ref)
  
  def GetGerritRefInfo(self, index):
    """
    'currentPatchSet': {'ref': 'refs/changes/32/256832/3'
    """
    if index >= len(self._gerrit_ref_info):
      LOG(LEVEL_ERROR, 'The count of the Ref info is [{}], but you set index [{}]'.format(len(self._gerrit_ref_info), index))
    return self._gerrit_ref_info[index]


  def SetWorkingPath(self, path):
    if len(path) <= len(_WORKING_ROOT_PATH):
      LOG(LEVEL_ERROR, 'Working path should under the [{}], but you set [{}]'.format(_WORKING_ROOT_PATH, path))
    if path[:len(_WORKING_ROOT_PATH)] != _WORKING_ROOT_PATH:
      LOG(LEVEL_ERROR, 'Working path you set do not support, please check. Path should start with [{}], but you set [{}]'.format(_WORKING_ROOT_PATH, path))
    self._working_dir = path

  def GetWorkingPath(self) -> str:
    if len(self._working_dir) <= len(_WORKING_ROOT_PATH):
      LOG(LEVEL_ERROR, 'Working path not set yet, please set it first: [{}]'.format(self._working_dir))
    if self._working_dir[:len(_WORKING_ROOT_PATH)] != _WORKING_ROOT_PATH:
      LOG(LEVEL_ERROR, 'Working path is wrong, please check, Path should start with [{}], but now is [{}]'.format(_WORKING_ROOT_PATH, self._working_dir))
    return self._working_dir


  def SetCodePath(self, path):
    if len(path) <= len(self.GetWorkingPath()):
      LOG(LEVEL_ERROR, 'Code path should under the [{}], but you set [{}]'.format(self.GetWorkingPath(), path))
    if path[:len(self.GetWorkingPath())] != self.GetWorkingPath():
      LOG(LEVEL_ERROR, 'Code path you set do not support, please check. Path should start with [{}], but you set [{}]'.format(self.GetWorkingPath(), path))
    self._code_dir = path

  def GetCodePath(self) -> str:
    if len(self._code_dir) <= len(self.GetWorkingPath()):
      LOG(LEVEL_ERROR, 'Code path not set yet, please set it first: [{}]'.format(self._code_dir))
    if self._code_dir[:len(self.GetWorkingPath())] != self.GetWorkingPath():
      LOG(LEVEL_ERROR, 'Code path is wrong, please check. Path should start with [{}], but now is [{}]'.format(self.GetWorkingPath(), self._code_dir))
    return self._code_dir    


  def _SetGerritFileInfo(self, files):
    """
    [
      {'file': '/COMMIT_MSG', 'type': 'ADDED', 'insertions': 18, 'deletions': 0}, 
      {'file': 'app/ux/src/ux_box.c', 'type': 'MODIFIED', 'insertions': 8, 'deletions': 0}, 
      {'file': 'app/ux/src/ux_sensor_interceptor.c', 'type': 'MODIFIED', 'insertions': 1, 'deletions': -1}, 
      {'file': 'include/platform/frameworks/utils/kernel_property.h', 'type': 'MODIFIED', 'insertions': 3, 'deletions': 0}, 
      {'file': 'platform/frameworks/property/include/kv_adapter.h', 'type': 'MODIFIED', 'insertions': 3, 'deletions': 0}, 
      {'file': 'platform/frameworks/property/property.c', 'type': 'MODIFIED', 'insertions': 7, 'deletions': -1}, 
      {'file': 'platform/frameworks/property/src/bes/kv_adapter.c', 'type': 'MODIFIED', 'insertions': 14, 'deletions': 0}, 
      {'file': 'platform/frameworks/property/src/wq/kv_adapter.c', 'type': 'MODIFIED', 'insertions': 11, 'deletions': -1}
    ]
    """
    self._gerrit_file_list.append(files)
  
  def GetGerritFileInfo(self, index):
    if index >= len(self._gerrit_file_list):
      LOG(LEVEL_ERROR, 'The count of the file list Number is [{}], but you set index [{}]'.format(len(self._gerrit_file_list), index))
    return self._gerrit_file_list[index]


  def _SetProjectName(self, name):
    """
    ts6239
    """
    if not _PROJECT_CONFIG_INFO.get(name.upper()):
      LOG(LEVEL_ERROR, 'We can not find the project for [{}]'.format(name))
    self._project_name = name.upper()

  def GetProjectName(self):
    if not self._project_name:
      LOG(LEVEL_ERROR, 'Project Name not set yet, please set it first')
    return self._project_name


  def _CheckGerritOpenStatus(self, status):
    """
    'open': True
    """
    if status != True:
      LOG(LEVEL_ERROR, 'The gerrit status is not OPEN, please check')


  def GetCodePullCmd(self):
    cmd = _CODE_PULL_CMD_TEMPLATE % (self.account_id, _PROJECT_CONFIG_INFO.get(self._project_name).get('code_server'), _PROJECT_CONFIG_INFO.get(self._project_name).get('manifest'))
    return cmd


  def GetEarphoneBuildCmd(self) -> str:
    cmd = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_cmd')
    if not cmd:
      LOG(LEVEL_ERROR, 'Build CMD get fail, please check the _PROJECT_CONFIG_INFO')
      return ''

    return cmd


  def GetEarphoneBuildPath(self) -> str:
    if not self.GetCodePath():
      LOG(LEVEL_ERROR, 'Cannot get earphone build path, please set the Code path first')
    path = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_dir')
    if not path:
      LOG(LEVEL_ERROR, 'Cannot get earphone build path, please check the earphone_build_dir in _PROJECT_CONFIG_INFO')
      return ''

    # test_file = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_test_file')
    # if not test_file:
    #   LOG(LEVEL_ERROR, 'Cannot get earphone build test file, Please check the earphone_build_test_file in _PROJECT_CONFIG_INFO')
    # test_file_path = self.GetCodePath() +  test_file
    # if not os.path.isfile(test_file_path):
    #   LOG(LEVEL_ERROR, 'Cannot get earphone build test file, please check if the file exist')
    return self.GetCodePath() + path
  
  def GetEarphoneBuildOutputPath(self) -> str:
    if not self.GetCodePath():
      LOG(LEVEL_ERROR, 'Cannot get earphone build path, please set the Code path first')
    path = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_output_path')
    if not path:
      LOG(LEVEL_ERROR, 'Cannot get earphone build path, please check the earphone_build_output_path in _PROJECT_CONFIG_INFO')
      return ''
    return self.GetCodePath() + path


  def GetEarphoneBuildTestFilePath(self) -> str:
    if not self.GetCodePath():
      LOG(LEVEL_ERROR, 'Cannot get earphone build path, please set the Code path first')
    test_file = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_test_file')
    if not test_file:
      LOG(LEVEL_ERROR, 'Cannot get earphone build test file, Please check the earphone_build_test_file in _PROJECT_CONFIG_INFO')
    return self.GetCodePath() +  test_file


  def GetEarphoneCopyFileList(self) -> list:
    work_path = self.GetCodePath()
    path  = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_output_file_path')
    filelist = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('earphone_build_output_file_list')
    ret = []
    if (not path) or (not filelist):
      LOG(LEVEL_ERROR, 'Get earphone copy file list fail. [{}] [{}]'.format(path, filelist))
    for file in filelist:
      ret.append(work_path + path + file)
    
    LOG(LEVEL_INFO, filelist)
    return ret


  def GetCherrypickPathByGerryProjectName(self, name) -> str:
    if not self._manifest_project_path_dict:
      LOG(LEVEL_ERROR, "Manifest xml not parse yet.")
    for node in self._manifest_project_path_dict:
      if node.get('name') == name:
        return self.GetCodePath() + node.get('path')
    LOG(LEVEL_ERROR, 'Cannot find the project [{}] in [{}] configuration'.format(name, self.GetProjectName()))
    return ''


  def GetCherrypickCommand(self, index) -> str:
    if not self._cherrypick_cmd_list:
      LOG(LEVEL_ERROR, 'Cherrypick command not set yet.')
    if index >= len(self._cherrypick_cmd_list):
      LOG(LEVEL_ERROR, 'Cherrypick command index error, we have [{}], but you want [{}]'.format(len(self._cherrypick_cmd_list), index + 1))
    return self._cherrypick_cmd_list[index]


  def GetImagePath(self, product, build_type) -> str:
    if not self._image_path:
      LOG(LEVEL_ERROR, 'image path not set yet, please check')
    if (product == 'earbud') or (product == 'box'):
      if (build_type == 'org') or (build_type == 'patch'):
        return self.GetWorkingPath() + self._image_path.get(product).get(build_type)
    LOG(LEVEL_ERROR, 'product [{}] or build_type [{}] error, please check'.format(product, build_type))
    return ''


  def GetImageFilePath(self, product, build_type, img_type) -> str:
    _supported_img_type = ['map', 'bin']
    if not self.GetImagePath(product, build_type):
      LOG(LEVEL_ERROR, 'Get image path fail when get file path, please check')
    if img_type not in _supported_img_type:
      LOG(LEVEL_ERROR, 'Get image file path fail, please check img_type you set')
    return self.GetImagePath(product, build_type) + _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('img_file_dict').get(product).get(img_type)


  def GetEarbudPlatformType(self) -> str:
    return _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('platform_type')

  def ParseGerritInfo(self):
    if not self.org_gerrit_data:
      LOG(LEVEL_ERROR, "Please set 'org_gerrit_data' first")
      return
    if len(self.org_gerrit_data) <= 1:
      LOG(LEVEL_ERROR, 'No gerrit info found.')
    for line_num in range(0, len(self.org_gerrit_data) - 1):
      json_data = json.loads(self.org_gerrit_data[line_num])
      if json_data:
        if json_data.get('project'):
          self._SetGerritProjectInfo(json_data.get('project'))
        else:
          LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('project'))

        if json_data.get('branch'):
          self._SetGerritBranchInfo(json_data.get('branch'))
        else:
          LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('branch'))

        if json_data.get('number'):
          self._SetGerritNumber(json_data.get('number'))
        else:
          LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('number'))

        if json_data.get('url'):
          self._SetGerritUrl(json_data.get('url'))
        else:
          LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('url'))

        if json_data.get('currentPatchSet'):
          if json_data.get('currentPatchSet').get('number'):
            self._SetGerritPatchsetNum(json_data.get('currentPatchSet').get('number'))
          else:
            LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('currentPatchSet.number'))

          if json_data.get('currentPatchSet').get('ref'):
            self._SetGerritRefInfo(json_data.get('currentPatchSet').get('ref'))
          else:
            LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('ref'))

          if json_data.get('currentPatchSet').get('files'):
            self._SetGerritFileInfo(json_data.get('currentPatchSet').get('files'))
          else:
            LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('currentPatchSet.files'))
        else:
          LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('currentPatchSet'))

        if json_data.get('open'):
          self._CheckGerritOpenStatus(json_data.get('open'))
        else:
          LOG(LEVEL_ERROR, 'Cannot find [{}] info in gerrit'.format('open status'))
  
    self.SetWorkingPath(_WORKING_ROOT_PATH + self.change_id[-8:] + '/')
    self.SetCodePath(self.GetWorkingPath() + 'code/')

    LOG(LEVEL_INFO, 'Start parsing cherrypick command')
    cherrypick_server = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('cherrypick_server')
    for index in range(0, self.GetGerritProjectCount()):
      cmd = _CHERRYPICK_CMD_TEMPLATE % (self.account_id, cherrypick_server, self.GetGerritProjectInfo(index), self.GetGerritRefInfo(index))
      self._cherrypick_cmd_list.append(cmd)
    LOG(LEVEL_INFO, 'Parsing cherrypick command finished')

  def ParseProjectManifestInfo(self):
    LOG(LEVEL_INFO, 'Start parsing manifest: [{}]'.format(self.GetProjectName()))
    manifest = _PROJECT_CONFIG_INFO.get(self.GetProjectName()).get('manifest')
    if not manifest:
      LOG(LEVEL_ERROR, 'Cannot fild manifest configuration in _PROJECT_CONFIG_INFO, please check')
    manifest_path = self.GetCodePath() + '.repo/manifests/' + manifest
    xml_data = XMLET.parse(manifest_path)
    root = xml_data.getroot()
    for child in root:
      if child.tag == 'project':
        self._manifest_project_path_dict.append(child.attrib)
    LOG(LEVEL_INFO, 'Parsing manifest Finished: [{}]'.format(self.GetProjectName()))


_guardInfo = _GuardInfo()


def _QueryGerritInfo():
  query_command = "ssh -p 29418 %s@gerrit.huaqin.com gerrit query %s --current-patch-set --format JSON --files" % (_guardInfo.account_id, _guardInfo.change_id)

  ret = TIMEOUT_SHELL_COMMAND(query_command, capture_output=True, timeout=30)
  if ret[0]:
    LOG(LEVEL_ERROR, 'Query gerrit info fail, please check the return code: [{}]'.format(ret[0]))
  # for line in gerrit_lines:
  #   LOG(LEVEL_INFO, json.loads(line))
  _guardInfo.org_gerrit_data = ret[1]
  _guardInfo.ParseGerritInfo()


def _InitReviewerWorkingEnv():
  _NEED_DELETE_EXISTED_DIR = True
  if not _NEED_DELETE_EXISTED_DIR:
    return

  LOG(LEVEL_INFO, 'Init the working path: [{}]'.format(_guardInfo.GetCodePath()))

  if os.path.exists(_guardInfo.GetWorkingPath()):
    LOG(LEVEL_INFO, 'Removing the already existing dir: [{}]'.format(_guardInfo.GetWorkingPath()))
    ret = TIMEOUT_SHELL_COMMAND('rm -rf %s' % (_guardInfo.GetWorkingPath()), timeout=90)
    if ret[0]:
      LOG(LEVEL_ERROR, 'Fail to delete the exist working path: [{}]'.format(_guardInfo.GetWorkingPath()))

  os.makedirs(_guardInfo.GetWorkingPath())
  if (not os.path.exists(_guardInfo.GetWorkingPath())) or (not os.access(_guardInfo.GetWorkingPath(), mode=os.W_OK)):
    LOG(LEVEL_ERROR, 'Dir cannot be create or no access to it [{}]'.format(_guardInfo.GetWorkingPath()))

  os.mkdir(_guardInfo.GetCodePath())
  if (not os.path.exists(_guardInfo.GetCodePath())) or (not os.access(_guardInfo.GetCodePath(), mode=os.W_OK)):
    LOG(LEVEL_ERROR, 'Dir cannot be create or no access to it [{}]'.format(_guardInfo.GetCodePath()))

  LOG(LEVEL_INFO, 'Init the working environment successful: [{}]'.format(_guardInfo.GetWorkingPath()))


def _PullProjectCode():
  _NEED_PULL_CODE = True
  if not _NEED_PULL_CODE:
    return

  LOG(LEVEL_INFO, 'Starting to pull the code to dir [{}]'.format(_guardInfo.GetWorkingPath()))
  pull_command = _guardInfo.GetCodePullCmd()

  ret = TIMEOUT_SHELL_COMMAND(command=pull_command, timeout=900, cwd=_guardInfo.GetCodePath())
  if ret[0]:
    LOG(LEVEL_ERROR, 'Pull code fail, please check. cmd:[{}]'.format(pull_command))
  test_dir = _guardInfo.GetCodePath() + 'earphone/'
  if (not os.path.exists(test_dir)) or (not os.access(test_dir, mode=os.W_OK)):
    LOG(LEVEL_ERROR, 'Dir cannot be create or no access to it [{}]'.format(test_dir))

  LOG(LEVEL_INFO, 'Pull the code successful [{}]'.format(_guardInfo.GetCodePath()))


def _BuildEarphone(copy_dst_path):
  _NEED_DO_BUILD = True
  if not _NEED_DO_BUILD:
    return
  LOG(LEVEL_INFO, 'Start to build the Earphone')
  build_path = _guardInfo.GetEarphoneBuildPath()
  build_cmd = _guardInfo.GetEarphoneBuildCmd()
  output_path = _guardInfo.GetEarphoneBuildOutputPath()
  test_file = _guardInfo.GetEarphoneBuildTestFilePath()
  
  if not os.path.isfile(test_file):
    LOG(LEVEL_ERROR, 'Cannot find the earphone test file [{}], please check.'.format(test_file))
  if os.path.exists(output_path):
    ret = TIMEOUT_SHELL_COMMAND('rm -rf %s' % (output_path), timeout=90)
    if ret[0]:
      LOG(LEVEL_ERROR, 'Cannot delete existing output dir: [ret={}] [{}]'.format(ret, output_path))
  if not os.access(build_path, mode=os.O_RDWR):
    LOG(LEVEL_ERROR, 'Cannot access build path [{}]'.format(build_path))
    
  ret = TIMEOUT_SHELL_COMMAND(build_cmd, timeout=900, cwd=build_path)
  if ret[0]:
    LOG(LEVEL_ERROR, 'Earphone build command execute Fail, ret = [{}]'.format(ret[0]))
  LOG(LEVEL_INFO, 'Earphone build successful.')
  
  LOG(LEVEL_INFO, 'Start copy output file to [{}]'.format(copy_dst_path))
  if os.path.exists(copy_dst_path):
    ret = TIMEOUT_SHELL_COMMAND('rm -rf %s' % (copy_dst_path), timeout=30)
    if ret[0]:
      LOG(LEVEL_ERROR, 'Cannot delete the existing copy destination dir: [{}]'.format(copy_dst_path))
  os.makedirs(copy_dst_path)
  if not os.path.exists(copy_dst_path):
    LOG(LEVEL_ERROR, 'Create copy destination dir fail: [{}]'.format(copy_dst_path))

  for file in _guardInfo.GetEarphoneCopyFileList():
    ret = TIMEOUT_SHELL_COMMAND('cp %s %s' % (file, copy_dst_path), timeout=10)
    if ret[0]:
      LOG(LEVEL_ERROR, 'Earphone copy file fail: from=[{}] to=[{}]'.format(file, copy_dst_path))
  LOG(LEVEL_INFO, 'Copy output files successful [{}]'.format(copy_dst_path))


def _CherrypickThePatch():
  _NEED_DO_CHERRYPICK = True
  if not _NEED_DO_CHERRYPICK:
    return
  'git fetch ssh://101002563@gerrit.huaqin.com:29418/HQ_BD1SW_PA1952/earphone refs/changes/78/256878/1 && git cherry-pick FETCH_HEAD'
  for index in range( _guardInfo.GetGerritProjectCount()):
    cherrypick_cmd = _guardInfo.GetCherrypickCommand(index)
    cherrypick_path = _guardInfo.GetCherrypickPathByGerryProjectName(_guardInfo.GetGerritProjectInfo(index))
    ret = TIMEOUT_SHELL_COMMAND(cherrypick_cmd, timeout=120, cwd=cherrypick_path)
    if ret[0]:
      LOG(LEVEL_ERROR, 'Cherry-pick fail: [{}] [{}]'.format(cherrypick_cmd, cherrypick_path))


def _DoBinSizeCheck():
  org_bin_path = _guardInfo.GetImageFilePath('earbud', 'org', 'bin')
  patch_bin_path = _guardInfo.GetImageFilePath('earbud', 'patch', 'bin')
  org_bin_size = os.path.getsize(org_bin_path)
  patch_bin_size = os.path.getsize(patch_bin_path)
  diff_size = patch_bin_size - org_bin_size
  LOG(LEVEL_INFO, 'patch_bin_size: [{}] - org_bin_size [{}] = [{}] Bytes'.format(patch_bin_size, org_bin_size, diff_size))
  pass


def _DoBesFreeRamCheck():
  _VALID_FREERAM_LIST = ['__free_ram', '__free_ramcp', '__free_ramcpx', '__free_fram']
  def _FindFreeRamLines(path) -> dict:
    _ALL_FREERAM_PATTERN = re.compile(r'__free_ram|__free_fram')
    ret_dict = {}
    fd = open(path, mode='r', encoding='utf8')
    lines = fd.readlines()
    fd.close()
    for line in lines:
      if _ALL_FREERAM_PATTERN.search(line):
        line = line.strip()
        info = re.split(r' +', line, 2)
        if info[1] not in _VALID_FREERAM_LIST:
          continue
        if info[0][:2] != '0x':
          continue
        ret_dict.setdefault(info[1], info[0])
    if len(ret_dict) != 4:
      LOG(LEVEL_ERROR, 'BES free ram check fail, the lines we found are [{}]'.format(ret_dict))
    return ret_dict

  org_map_path = _guardInfo.GetImageFilePath('earbud', 'org', 'map')
  patch_map_path = _guardInfo.GetImageFilePath('earbud', 'patch', 'map')
  org_ram_dict = _FindFreeRamLines(org_map_path)
  patch_ram_dict = _FindFreeRamLines(patch_map_path)
  LOG(LEVEL_INFO, 'ORG  : [{}]'.format(org_ram_dict))
  LOG(LEVEL_INFO, 'PATCH: [{}]'.format(patch_ram_dict))
  for item in _VALID_FREERAM_LIST:
    org_hex_str = org_ram_dict.get(item)
    org_dec = int(org_hex_str, base=16)
    patch_hex_str = patch_ram_dict.get(item)
    patch_dec = int(patch_hex_str, base=16)
    diff_dec = patch_dec - org_dec
    LOG(LEVEL_INFO, '[{:<13}]: PATCH[{}][{:^7}] - ORG[{}][{:^7}] = [{}]'.format(item, patch_hex_str, patch_dec, org_hex_str, org_dec, diff_dec))


def _DoResourceCheck():
  _DoBinSizeCheck()
  if _guardInfo.GetEarbudPlatformType() == 'bes':
    _DoBesFreeRamCheck()
    # _DoBesResourceDetailCheck()


def _DoTwsLintCheck():
  return


def DoReview(res_check=True, lint_check=True):
  # step 1: set project work dir
  _InitReviewerWorkingEnv()
  # step 2: pull the project orginal code
  _PullProjectCode()
  _guardInfo.ParseProjectManifestInfo()
  # step 3: build the orginal code and save the output file
  _BuildEarphone(_guardInfo.GetImagePath('earbud', 'org'))
  # step 4: cherry pick the patch
  _CherrypickThePatch()
  # step 5: build the patch and save the output file
  _BuildEarphone(_guardInfo.GetImagePath('earbud', 'patch'))
  # step 6: do resource check
  if res_check:
    _DoResourceCheck()
  # step 7: do tws lint check
  if lint_check:
    _DoTwsLintCheck()


def GuardInit(output_type='offline', account_id='', change_id='', project_name=''):
  _guardInfo.output_type = output_type
  _guardInfo.account_id = account_id
  _guardInfo.change_id = change_id
  _guardInfo._SetProjectName(project_name)
  _QueryGerritInfo()
  pass


if __name__ == '__main__':
  parser = argparse.ArgumentParser(description='')
  parser.add_argument('--type', '-t', required=True, metavar='online|offline', choices=['online', 'offline'], help='Output the result to gerrit (online) or stdio (offline)')
  parser.add_argument('--account', '-a', required=True, metavar='1010xxxxx', help='The gerrit account ID set on the PC')
  parser.add_argument('--changeid', '-c', required=True, metavar='ChangeID', help='The gerrit change ID you want to check')
  parser.add_argument('--project', '-p', required=True, metavar='ts6239|ts6351a|ts6569a|all', help='The project name of the change, "all" for check all project we have')
  args = parser.parse_args()

  GuardInit(args.type, args.account, args.changeid, args.project)
  DoReview()
